
#include <string.h> // memcpy
#include "ka-graphics/bitmap.h"

using namespace ka;

static uint32_t _samp_size(Bitmap::Format format) {
    uint32_t ss = 0;
    switch (format) {
        case Bitmap::FORMAT_RGB565:
            ss = 2;
            break;
        case Bitmap::FORMAT_RGBA8888:
            ss = 4;
            break;
        default:
            break;
    }
    return ss;
}

static void _pack_rgb565(uint8_t *pos, Color c) {
    uint16_t s = (uint16_t)((c.red() & 0xf8) << 8) 
    | ((c.green() & 0xfc) << 3) 
    | (c.blue() & 0xf8);
    
    pos[0] = ((uint8_t *)(&s))[0];
    pos[1] = ((uint8_t *)(&s))[1];    
}

static Color _unpack_rgb565(const uint8_t *pos) {
    Color c;
    uint8_t s;
    
    ((uint8_t *)(&s))[0] = pos[0];
    ((uint8_t *)(&s))[1] = pos[1];
    
    c.red((s >> 11) & 0x1f);
    c.green((s >> 5) & 0x3f);
    c.blue(s & 0x1f);
    return c;
}

static void _pack_rgba8888(uint8_t *pos, Color c) {
    pos[0] = c.red();
    pos[1] = c.green();
    pos[2] = c.blue();
    pos[3] = c.alpha();
}

static Color _unpack_rgba8888(const uint8_t *pos) {
    return Color(pos[0], pos[1], pos[2], pos[3]);
}

Color::Color() {
    _u.packed = 0;
}

Color::Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
    _u.channels[0] = red;
    _u.channels[1] = green;
    _u.channels[2] = blue;
    _u.channels[3] = alpha;
}

Color Color::from4f(float red, float green, float blue, float alpha) {
    return Color((uint8_t)(red * 255.0f),
                 (uint8_t)(green * 255.0f),
                 (uint8_t)(blue * 255.0f),
                 (uint8_t)(alpha * 255.0f));
}

void Color::to4f(Color c, float f[]) {
    f[0] = c.red() / 255.0f;
    f[1] = c.green() / 255.0f;
    f[2] = c.blue() / 255.0f;
    f[3] = c.alpha() / 255.0f;
}

Bitmap::Bitmap(uint32_t width, uint32_t height, Format format) 
: _data(NULL),
_stride(0),
_width(0),
_height(0),
_format(format) {
    uint32_t stride = width * _samp_size(format);
    size_t dataSize = stride * height;
    if (dataSize > 0) {
        _data = (uint8_t *)malloc(dataSize);
        _stride = stride;
        _width = width;
        _height = height;
    }
}

Bitmap::~Bitmap() {
    free(_data);
}

void Bitmap::set(int32_t x, int32_t y, Color color) {
    uint8_t *pos = _data + (y * _stride) + (x * _samp_size(_format));
    if (pos > _data && pos < _data+_stride*_height) {
        switch (_format) {
            case Bitmap::FORMAT_RGB565:
                _pack_rgb565(pos, color);
                break;
            case Bitmap::FORMAT_RGBA8888:
                _pack_rgba8888(pos, color);
                break;
            default:
                break;
        }
    }
}

Color Bitmap::get(int32_t x, int32_t y) const {
    const uint8_t *pos = _data + (y * _stride) + (x * _samp_size(_format));
    Color c;
    
    if (pos > _data && pos < _data+_stride*_height) {
        switch (_format) {
            case Bitmap::FORMAT_RGB565:
                c = _unpack_rgb565(pos);
                break;
            case Bitmap::FORMAT_RGBA8888:
                c = _unpack_rgba8888(pos);
                break;
            default:
                break;
        }
    }
    
    return c;
}

uint32_t Bitmap::width() const {
    return _width;
}

uint32_t Bitmap::stride() const {
    return _stride;
}

uint32_t Bitmap::height() const {
    return _height;
}

uint8_t *Bitmap::dataPtr() {
    return _data;
}

const uint8_t *Bitmap::dataPtr() const {
    return _data;
}

static const uint16_t cached_channel_counts[] = {
    3, // FORMAT_RGB565
    4  // FORMAT_RGBA565
};

static const ColorChannel cached_channels[][4] = {
    { /* FORMAT_RGB565 */
        {ColorChannel::RED, 5}, 
        {ColorChannel::GREEN, 6}, 
        {ColorChannel::BLUE, 6}, 
        {ColorChannel::NONE, 0} 
    }, 
    { /* FORMAT_RGBA8888 */
        {ColorChannel::RED, 8}, 
        {ColorChannel::GREEN, 8},
        {ColorChannel::BLUE, 8}, 
        {ColorChannel::ALPHA, 8} 
    }
};

void Bitmap::getMetrics(BitmapMetrics &mout) const {
    mout.width = _width;
    mout.height = _height;
    mout.format = _format;
    mout.channelCount = cached_channel_counts[_format];
    memcpy(mout.channels, cached_channels[_format], sizeof(ColorChannel[4]));
}
